 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.console;

 import java.io.IOException ;
 import java.io.OutputStream ;

 import org.eclipse.swt.graphics.Color;
 import org.eclipse.ui.WorkbenchEncoding;
 import org.eclipse.ui.internal.console.IOConsolePartitioner;

 /**
  * OutputStream used to write to an IOConsole.
  * <p>
  * Clients are not intended to instantiate this class directly, instead
  * use <code>IOConsole.newOutputStream()</code>. Clients are not intended
  * to subclass this class.
  * </p>
  * <p>
  * Clients should avoid writing large amounts of output to this stream in the UI
  * thread. The console needs to process the output in the UI thread and if the client
  * hogs the UI thread writing output to the console, the console will not be able
  * to process the output.
  * </p>
  * @since 3.1
  */
 public class IOConsoleOutputStream extends OutputStream {
     /**
      * Flag indicating whether this stream has been closed.
      */
     private boolean closed = false;

     /**
      * The console's document partitioner.
      */
     private IOConsolePartitioner partitioner;
     
     /**
      * The console this stream is attached to.
      */
     private IOConsole console;
     
     /**
      * Flag indicating that the console should be activated when data
      * is written to this stream.
      */
     private boolean activateOnWrite = false;
     
     /**
      * The color used to decorate data written to this stream.
      */
     private Color color;
     
     /**
      * The font style used to decorate data written to this stream.
      */
     private int fontStyle;

     private String fEncoding;
     private String fDefaultEncoding = WorkbenchEncoding.getWorkbenchDefaultEncoding();

     private boolean fNeedsEncoding = false;

     private boolean prependCR;
     
     /**
      * Constructs a new output stream on the given console.
      *
      * @param console I/O console
      */
     IOConsoleOutputStream(IOConsole console) {
         this.console = console;
         this.partitioner = (IOConsolePartitioner) console.getPartitioner();
     }

     /**
      * Returns the font style used to decorate data written to this stream.
      *
      * @return the font style used to decorate data written to this stream
      */
     public int getFontStyle() {
         return fontStyle;
     }
     
     /**
      * Sets the font style to be used to decorate data written to this stream.
      *
      * @param newFontStyle the font style to be used to decorate data written to this stream
      */
     public void setFontStyle(int newFontStyle) {
         if (newFontStyle != fontStyle) {
             int old = fontStyle;
             fontStyle = newFontStyle;
             console.firePropertyChange(this, IConsoleConstants.P_FONT_STYLE, new Integer (old), new Integer (fontStyle));
         }
     }
     
     /**
      * Returns whether the console this stream is writing to will be activated when this stream
      * is written to.
      *
      * @return whether the console this stream is writing to will be activated when this stream
      * is written to.
      */
     public boolean isActivateOnWrite() {
         return activateOnWrite;
     }

     /**
      * Sets whether to activate the console this stream is writing to when this stream
      * is written to.
      *
      * @param activateOnWrite whether the console this stream is writing to will be activated when this stream
      * is written to.
      */
     public void setActivateOnWrite(boolean activateOnWrite) {
         this.activateOnWrite = activateOnWrite;
     }
     
     /**
      * Sets the color of this stream. Use <code>null</code> to indicate
      * the default color.
      *
      * @param newColor color of this stream, or <code>null</code>
      */
     public void setColor(Color newColor) {
         Color old = color;
         if (old == null || !old.equals(newColor)) {
             color = newColor;
             console.firePropertyChange(this, IConsoleConstants.P_STREAM_COLOR, old, newColor);
         }
     }
     
     /**
      * Returns the color of this stream, or <code>null</code>
      * if default.
      *
      * @return the color of this stream, or <code>null</code>
      */
     public Color getColor() {
         return color;
     }
     
     /**
      * Returns true if the stream has been closed
      * @return true is the stream has been closed, false otherwise.
      */
     public synchronized boolean isClosed() {
         return closed;
     }
     
     /*
      * (non-Javadoc)
      * @see java.io.OutputStream#close()
      */
     public synchronized void close() throws IOException {
         if(closed) {
             throw new IOException ("Output Stream is closed"); //$NON-NLS-1$
 }
         if (prependCR) { // force writing of last /r
 prependCR = false;
             notifyParitioner("\r"); //$NON-NLS-1$
 }
         console.streamClosed(this);
         closed = true;
         partitioner = null;
     }

     /*
      * (non-Javadoc)
      * @see java.io.OutputStream#flush()
      */
     public void flush() throws IOException {
         if(closed) {
             throw new IOException ("Output Stream is closed"); //$NON-NLS-1$
 }
     }
     
     /*
      * (non-Javadoc)
      * @see java.io.OutputStream#write(byte[], int, int)
      */
     public void write(byte[] b, int off, int len) throws IOException {
         if (fNeedsEncoding) {
             encodedWrite(new String (b, off, len, fEncoding));
         } else {
             encodedWrite(new String (b, off, len));
         }
     }
     /*
      * (non-Javadoc)
      * @see java.io.OutputStream#write(byte[])
      */
     public void write(byte[] b) throws IOException {
         write(b, 0, b.length);
     }
     /*
      * (non-Javadoc)
      * @see java.io.OutputStream#write(int)
      */
     public void write(int b) throws IOException {
         write(new byte[] {(byte)b}, 0, 1);
     }
     
     /**
      * Writes a string to the attached console.
      *
      * @param str the string to write to the attached console.
      * @throws IOException if the stream is closed.
      */
     public synchronized void write(String str) throws IOException {
         if (fNeedsEncoding) {
             byte[] defaultBytes = str.getBytes();
             str = new String (defaultBytes, fEncoding);
         }
         encodedWrite(str);
     }
     
     private void encodedWrite(String encodedString) throws IOException {
         if(closed) {
             throw new IOException ("Output Stream is closed"); //$NON-NLS-1$
 }
         if (prependCR){
             encodedString="\r"+encodedString; //$NON-NLS-1$
 prependCR=false;
         }
         if (encodedString.endsWith("\r")) { //$NON-NLS-1$
 prependCR = true;
             encodedString = new String (encodedString.substring(0, encodedString.length()-1));
         }
         notifyParitioner(encodedString);
     }

     private void notifyParitioner(String encodedString) throws IOException {
         try {
             partitioner.streamAppended(this, encodedString);

             if (activateOnWrite) {
                 console.activate();
             } else {
                 ConsolePlugin.getDefault().getConsoleManager().warnOfContentChange(console);
             }
         } catch (IOException e) {
             if (!closed) {
                 close();
             }
             throw e;
         }
     }

     /**
      * Sets the character encoding used to interpret characters written to this steam.
      *
      * @param encoding encoding identifier
      */
     public void setEncoding(String encoding) {
         fEncoding = encoding;
         fNeedsEncoding = (fEncoding!=null) && (!fEncoding.equals(fDefaultEncoding));
     }
 }

